/*
 * Copyright (c) 2024 Fraunhofer FOKUS and others. All rights reserved.
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contact: mosaic@fokus.fraunhofer.de
 */

package org.eclipse.mosaic.fed.application.ambassador.simulation.perception;

import org.eclipse.mosaic.fed.application.ambassador.SimulationKernel;
import org.eclipse.mosaic.fed.application.app.api.perception.BasicSensorModule;
import org.eclipse.mosaic.lib.objects.environment.EnvironmentEvent;
import org.eclipse.mosaic.lib.objects.environment.Sensor;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * An implementation of the {@link BasicSensorModule} based on {@link EnvironmentEvent}s
 * broadcast through {@link org.eclipse.mosaic.interactions.environment.EnvironmentSensorUpdates},
 * e.g., generated by the mosaic-environment simulator.
 */
public class EnvironmentBasicSensorModule implements BasicSensorModule {

    private final Map<String, EnvironmentEvent> environmentEvents = new HashMap<>();

    private boolean enabled = false;

    @Override
    public void enable() {
        enabled = true;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    @Override
    public void disable() {
        enabled = false;
    }

    @Override
    public <T> Optional<T> getSensorValue(Sensor<T> sensor) {
        EnvironmentEvent event = environmentEvents.get(sensor.getName());
        if (event != null && event.from <= SimulationKernel.SimulationKernel.getCurrentSimulationTime()
                && event.until >= SimulationKernel.SimulationKernel.getCurrentSimulationTime()) {
            return Optional.ofNullable(sensor.translate(event.value));
        }
        return Optional.empty();
    }

    /**
     * Adds a new {@link EnvironmentEvent} for the given {@link Sensor}.
     */
    public final void addEnvironmentEvent(String type, EnvironmentEvent environmentEvent) {
        if (isEnabled()) {
            environmentEvents.put(type, environmentEvent);
        }
    }

    /**
     * The events are mapped into a map on the type. With multiple events to a
     * same type, the last event is always taken. However, it should be part of
     * good form to delete the event you no longer need to save some memory.
     */
    public final void cleanPastEnvironmentEvents() {
        final Set<String> toRemove = new HashSet<>();

        for (Map.Entry<String, EnvironmentEvent> entrySet : environmentEvents.entrySet()) {
            String type = entrySet.getKey();
            EnvironmentEvent environmentEvent = entrySet.getValue();
            if (environmentEvent.until < SimulationKernel.SimulationKernel.getCurrentSimulationTime()) {
                toRemove.add(type);
            }
        }
        toRemove.forEach(environmentEvents::remove);
    }
}
